package com.ecyrd.jspwiki.workflow;

import java.security.Principal;
import java.util.Collection;
import java.util.List;
import java.util.Properties;

import junit.framework.TestCase;

import com.ecyrd.jspwiki.*;
import com.ecyrd.jspwiki.auth.Users;
import com.ecyrd.jspwiki.auth.WikiPrincipal;
import com.ecyrd.jspwiki.filters.BasicPageFilter;
import com.ecyrd.jspwiki.filters.FilterException;

public class ApprovalWorkflowTest extends TestCase
{
    WorkflowBuilder m_builder;
    TestEngine m_engine;
    WorkflowManager m_wm;
    DecisionQueue m_dq;


    protected void setUp() throws Exception
    {
        super.setUp();
        Properties props = new Properties();
        props.load(TestEngine.findTestProperties());

        // Explicitly turn on Admin approvals for page saves and our sample approval workflow
        props.put("jspwiki.approver.workflow.saveWikiPage", "Admin");
        props.put( "jspwiki.approver.workflow.approvalWorkflow", Users.JANNE );

        // Start the wiki engine
        m_engine = new TestEngine(props);
        m_wm = m_engine.getWorkflowManager();
        m_dq = m_wm.getDecisionQueue();
        m_builder = WorkflowBuilder.getBuilder( m_engine );
    }


    public void testBuildApprovalWorkflow() throws WikiException
    {

        Principal submitter = new WikiPrincipal( "Submitter" );
        String workflowApproverKey = "workflow.approvalWorkflow";
        Task prepTask = new TestPrepTask( "task.preSaveWikiPage" );
        String decisionKey = "decision.saveWikiPage";
        Fact[] facts = new Fact[3];
        facts[0] = new Fact("fact1",new Integer(1));
        facts[1] = new Fact("fact2","A factual String");
        facts[2] = new Fact("fact3",Outcome.DECISION_ACKNOWLEDGE);
        Task completionTask = new TestPrepTask( "task.saveWikiPage" );
        String rejectedMessageKey = "notification.saveWikiPage.reject";

        Workflow w = m_builder.buildApprovalWorkflow(submitter, workflowApproverKey,
                                                   prepTask, decisionKey, facts,
                                                   completionTask, rejectedMessageKey);
        w.setWorkflowManager( m_engine.getWorkflowManager() );

        // Check to see if the workflow built correctly
        assertFalse( w.isStarted() || w.isCompleted() || w.isAborted() );
        assertNull( w.getCurrentStep() );
        assertEquals( "workflow.approvalWorkflow", w.getMessageKey() );
        assertEquals( Workflow.CREATED, w.getCurrentState() );
        assertEquals( new WikiPrincipal("Submitter"), w.getOwner() );
        assertEquals( m_engine.getWorkflowManager(), w.getWorkflowManager() );
        assertEquals( 0, w.getHistory().size() );

        // Our dummy "task complete" attributes should still be null
        assertNull( w.getAttribute( "task.preSaveWikiPage") );
        assertNull( w.getAttribute( "task.saveWikiPage") );

        // Start the workflow
        w.start();

        // Presave complete attribute should be set now, and current step should be Decision
        Step decision = w.getCurrentStep();
        assertTrue( decision instanceof Decision );
        assertEquals( 2, w.getHistory().size() );
        assertEquals( prepTask, w.getHistory().get( 0 ) );
        assertTrue( w.getHistory().get( 1 ) instanceof Decision );
        assertNotNull( w.getAttribute( "task.preSaveWikiPage") );
        assertEquals( new WikiPrincipal( Users.JANNE ), decision.getActor() );
        assertEquals( decisionKey, decision.getMessageKey() );
        List decisionFacts = ((Decision)decision).getFacts();
        assertEquals( 3, decisionFacts.size() );
        assertEquals( facts[0], decisionFacts.get(0) );
        assertEquals( facts[1], decisionFacts.get(1) );
        assertEquals( facts[2], decisionFacts.get(2) );

        // Check that our predecessor/successor relationships are ok
        assertEquals( decision, prepTask.getSuccessor( Outcome.STEP_COMPLETE ) );
        assertEquals( null, prepTask.getSuccessor( Outcome.STEP_ABORT ) );
        assertEquals( null, prepTask.getSuccessor( Outcome.STEP_CONTINUE ) );
        assertEquals( null, decision.getSuccessor( Outcome.DECISION_ACKNOWLEDGE ) );
        assertEquals( null, decision.getSuccessor( Outcome.DECISION_HOLD ) );
        assertEquals( null, decision.getSuccessor( Outcome.DECISION_REASSIGN ) );
        assertEquals( completionTask, decision.getSuccessor( Outcome.DECISION_APPROVE ) );

        // The "deny" notification should use the right key
        Step notification = decision.getSuccessor( Outcome.DECISION_DENY );
        assertNotNull( notification );
        assertEquals( rejectedMessageKey, notification.getMessageKey() );
        assertTrue( notification instanceof SimpleNotification );

        // Now, approve the Decision and everything should complete
        ((Decision)decision).decide( Outcome.DECISION_APPROVE );
        assertTrue( w.isCompleted() );
        assertNull( w.getCurrentStep() );
        assertEquals( 3, w.getHistory().size() );
        assertEquals( completionTask, w.getHistory().get( 2 ) );
        assertTrue( completionTask.isCompleted() );
        assertEquals( Outcome.STEP_COMPLETE, completionTask.getOutcome() );
    }

    public void testBuildApprovalWorkflowDeny() throws WikiException
    {
        Principal submitter = new WikiPrincipal( "Submitter" );
        String workflowApproverKey = "workflow.approvalWorkflow";
        Task prepTask = new TestPrepTask( "task.preSaveWikiPage" );
        String decisionKey = "decision.saveWikiPage";
        Fact[] facts = new Fact[3];
        facts[0] = new Fact("fact1",new Integer(1));
        facts[1] = new Fact("fact2","A factual String");
        facts[2] = new Fact("fact3",Outcome.DECISION_ACKNOWLEDGE);
        Task completionTask = new TestPrepTask( "task.saveWikiPage" );
        String rejectedMessageKey = "notification.saveWikiPage.reject";

        Workflow w = m_builder.buildApprovalWorkflow(submitter, workflowApproverKey,
                                                   prepTask, decisionKey, facts,
                                                   completionTask, rejectedMessageKey);
        w.setWorkflowManager( m_engine.getWorkflowManager() );

        // Start the workflow
        w.start();

        // Now, deny the Decision and the submitter should see a notification
        Step step = w.getCurrentStep();
        assertTrue( step instanceof Decision );
        Decision decision = (Decision)step;
        decision.decide( Outcome.DECISION_DENY );
        assertFalse( w.isCompleted() );

        // Check that the notification is ok, then acknowledge it
        step = w.getCurrentStep();
        assertTrue( step instanceof SimpleNotification );
        assertEquals( rejectedMessageKey, step.getMessageKey() );
        SimpleNotification notification = (SimpleNotification)step;
        notification.acknowledge();

        // Workflow should be complete now
        assertTrue( w.isCompleted() );
        assertNull( w.getCurrentStep() );
        assertEquals( 3, w.getHistory().size() );
        assertEquals( notification, w.getHistory().get( 2 ) );
    }

    public void testSaveWikiPageWithApproval() throws WikiException
    {
        // Create a sample test page and try to save it
        String pageName = "SaveWikiPageWorkflow-Test" + System.currentTimeMillis();
        String text = "This is a test!";
        try
        {
            m_engine.saveTextAsJanne(pageName, text);
        }
        catch ( DecisionRequiredException e )
        {
            // Swallow exception, because it is expected...
        }

        // How do we know the workflow works? Well, first of all the page shouldn't exist yet...
        assertFalse( m_engine.pageExists(pageName));

        // Second, GroupPrincipal Admin should see a Decision in its queue
        Collection decisions = m_dq.getActorDecisions( m_engine.adminSession() );
        assertEquals(1, decisions.size());

        // Now, approve the decision and it should go away, and page should appear.
        Decision decision = (Decision)decisions.iterator().next();
        decision.decide(Outcome.DECISION_APPROVE);
        assertTrue( m_engine.pageExists(pageName));
        decisions = m_dq.getActorDecisions( m_engine.adminSession() );
        assertEquals(0, decisions.size());

        // Delete the page we created
        m_engine.deletePage( pageName );
    }

    public void testSaveWikiPageWithRejection() throws WikiException
    {
        // Create a sample test page and try to save it
        String pageName = "SaveWikiPageWorkflow-Test" + System.currentTimeMillis();
        String text = "This is a test!";
        try
        {
            m_engine.saveTextAsJanne(pageName, text);
        }
        catch ( DecisionRequiredException e )
        {
            // Swallow exception, because it is expected...
        }

        // How do we know the workflow works? Well, first of all the page shouldn't exist yet...
        assertFalse( m_engine.pageExists(pageName));

        // ...and there should be a Decision in GroupPrincipal Admin's queue
        Collection decisions = m_dq.getActorDecisions( m_engine.adminSession() );
        assertEquals(1, decisions.size());

        // Now, DENY the decision and the page should still not exist...
        Decision decision = (Decision)decisions.iterator().next();
        decision.decide(Outcome.DECISION_DENY);
        assertFalse( m_engine.pageExists(pageName) );

        // ...but there should also be a notification decision in Janne's queue
        decisions = m_dq.getActorDecisions( m_engine.janneSession() );
        assertEquals(1, decisions.size());
        decision = (Decision)decisions.iterator().next();
        assertEquals(PageManager.SAVE_REJECT_MESSAGE_KEY, decision.getMessageKey());

        // Once Janne disposes of the notification, his queue should be empty
        decision.decide(Outcome.DECISION_ACKNOWLEDGE);
        decisions = m_dq.getActorDecisions( m_engine.janneSession() );
        assertEquals(0, decisions.size());
    }

    public void testSaveWikiPageWithException() throws WikiException
    {
        // Add a PageFilter that rejects all save attempts
        m_engine.getFilterManager().addPageFilter( new AbortFilter(), 0 );

        // Create a sample test page and try to save it
        String pageName = "SaveWikiPageWorkflow-Test" + System.currentTimeMillis();
        String text = "This is a test!";
        try
        {
            m_engine.saveTextAsJanne(pageName, text);
        }
        catch ( WikiException e )
        {
            assertTrue( e instanceof FilterException );
            assertEquals( "Page save aborted.", e.getMessage() );
            return;
        }
        fail( "Page save should have thrown a FilterException, but didn't." );
    }

    /**
     * Sample "prep task" that sets an attribute in the workflow indicating
     * that it ran successfully,
     * @author Andrew Jaquith
     */
    public static class TestPrepTask extends Task
    {
        private static final long serialVersionUID = 1L;

        public TestPrepTask( String messageKey )
        {
            super( messageKey );
        }

        public Outcome execute() throws WikiException
        {
            getWorkflow().setAttribute( getMessageKey(), "Completed" );
            setOutcome( Outcome.STEP_COMPLETE );
            return Outcome.STEP_COMPLETE;
        }

    }

    /**
     * Dummy PageFilter that always throws a FilterException during preSave operations.
     * @author Andrew Jaquith
     */
    public static class AbortFilter extends BasicPageFilter
    {
        public String preSave(WikiContext wikiContext, String content) throws FilterException
        {
            throw new FilterException( "Page save aborted." );
        }
    }

}
